package com.yc.spring;

import com.yc.spring.bbs.biz.UserBiz;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AOPApplicationContext extends AnnotationConfigApplicationContext {
    public AOPApplicationContext(Class configClass) throws Exception {
        super(configClass);

        // 1. 扫描所有的组件, 查找切面类
        Map<String, AspectInfo> aspectInfoMap = new HashMap<>();
        for (Map.Entry<String, BeanInfo> entry : IOC.entrySet()) {
            final String name = entry.getKey();
            final BeanInfo beanInfo = entry.getValue();
            final Aspect aspect = beanInfo.bean.getClass().getAnnotation(Aspect.class);
            if (aspect == null) {
                continue;
            }

            // 2. 分析切面类, 1个切点 : N 个增强方法  =>  Map
            final Method[] methods = beanInfo.bean.getClass().getDeclaredMethods();
            for (Method method : methods) {
                String pointcutExp = null;
                final Before before = method.getAnnotation(Before.class);
                if (before != null) {
                    pointcutExp = before.value();
                } else {
                    continue;
                }
                // 判断切点表达式是否引用了方法名切点 pointcut()
                if (pointcutExp.matches("\\w+\\(\\)")) {
                    final String pointcutMethodName = pointcutExp.replaceAll("(\\w+)\\(\\)", "$1");
                    final Method pointcutMethod = beanInfo.bean.getClass().getDeclaredMethod(pointcutMethodName);
                    pointcutExp = pointcutMethod.getAnnotation(Pointcut.class).value();
                }
                // 创建切点信息对象
                AspectInfo aspectInfo = aspectInfoMap.get(pointcutExp);
                if (aspectInfo == null) {
                    aspectInfo = new AspectInfo();
                    aspectInfo.pointcutExp = pointcutExp;
                    // 将切面组件对象保存到 info 中
                    aspectInfo.aspectObject = beanInfo.bean;
                    aspectInfoMap.put(pointcutExp, aspectInfo);
                }
                // 添加该切点的增强方法
                if (before != null) {
                    aspectInfo.befores.add(method);
                }
            }

        }

        // 打印
        aspectInfoMap.values().forEach(System.out::println);

        // 判断组件是否包含连接点方法
        for (Map.Entry<String, BeanInfo> entry : IOC.entrySet()) {
            final BeanInfo beanInfo = entry.getValue();
            final Method[] declaredMethods = beanInfo.bean.getClass().getDeclaredMethods();
            boolean isProxy = false;
            // 命名循环
            a: for (Method method : declaredMethods) {
                // 方法签名字符串
                final String methodString = method.toString();
                // 切点表达式判断
               b: for (String pointcutExp : aspectInfoMap.keySet()) {
                    // 切点表达式判断 转正则表达式
                    if(isAdvice(pointcutExp, methodString)){
                        // 包含连接点方法
                        System.out.println(methodString);
                        isProxy = true;
                        break a;
                    }
                }
            }
            if(isProxy){
                // 进行动态代理 => CGLIB
                Enhancer enhancer = new Enhancer();
                Object bean = beanInfo.bean;
                enhancer.setSuperclass(bean.getClass());
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        try {
                            // 判断当前方法是否拦截 (前置拦截)
                            for (AspectInfo aspectInfo : aspectInfoMap.values()) {
                                if (isAdvice(aspectInfo.pointcutExp, method.toString())) {
                                    for (Method before : aspectInfo.befores) {
                                        // 执行前置增强
                                        before.invoke(aspectInfo.aspectObject, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return method.getName();
                                            }

                                            @Override
                                            public String toLongString() {
                                                return method.toString();
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return beanInfo.bean;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return objects;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return null;
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        });
                                    }
                                }
                            }
                            final Object ret = methodProxy.invoke(bean, objects);
                            for (AspectInfo aspectInfo : aspectInfoMap.values()) {
                                if (isAdvice(aspectInfo.pointcutExp, method.toString())) {
                                    for (Method afterReturning : aspectInfo.afterReturnings) {
                                        // 执行返回增强
                                        afterReturning.invoke(aspectInfo.aspectObject, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return method.getName();
                                            }

                                            @Override
                                            public String toLongString() {
                                                return method.toString();
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return beanInfo.bean;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return objects;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return null;
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        }, ret);
                                    }
                                }
                            }
                            return ret;
                        } catch ( Throwable t){
                            for (AspectInfo aspectInfo : aspectInfoMap.values()) {
                                if (isAdvice(aspectInfo.pointcutExp, method.toString())) {
                                    for (Method afterThrowing : aspectInfo.afterThrowings) {
                                        // 执行异常增强
                                        afterThrowing.invoke(aspectInfo.aspectObject, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return method.getName();
                                            }

                                            @Override
                                            public String toLongString() {
                                                return method.toString();
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return beanInfo.bean;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return objects;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return null;
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        }, t);
                                    }
                                }
                            }
                        } finally {
                            for (AspectInfo aspectInfo : aspectInfoMap.values()) {
                                if (isAdvice(aspectInfo.pointcutExp, method.toString())) {
                                    for (Method after : aspectInfo.afters) {
                                        // 执行后置增强
                                        after.invoke(aspectInfo.aspectObject, new JoinPoint() {
                                            @Override
                                            public String toShortString() {
                                                return method.getName();
                                            }

                                            @Override
                                            public String toLongString() {
                                                return method.toString();
                                            }

                                            @Override
                                            public Object getThis() {
                                                return null;
                                            }

                                            @Override
                                            public Object getTarget() {
                                                return beanInfo.bean;
                                            }

                                            @Override
                                            public Object[] getArgs() {
                                                return objects;
                                            }

                                            @Override
                                            public Signature getSignature() {
                                                return null;
                                            }

                                            @Override
                                            public SourceLocation getSourceLocation() {
                                                return null;
                                            }

                                            @Override
                                            public String getKind() {
                                                return null;
                                            }

                                            @Override
                                            public StaticPart getStaticPart() {
                                                return null;
                                            }
                                        });
                                    }
                                }
                            }
                        }
                        return null;
                    }
                });
                // 生成代理对象
                final Object proxyBean = enhancer.create();
                // 用代理替换原有的 bean
                beanInfo.bean = proxyBean;
            }
        }

    }

    boolean isAdvice(String pointcutExp, String methodString){
        String regExp = pointcutExp.replaceAll("\\.{2}",".+");
        regExp = regExp.replaceAll("\\*",".+");
        regExp = regExp.replaceAll("execution\\((.+)\\)","$1");
        return methodString.matches(regExp);
    }

    /**
     * 实现流程
     * 1. 扫描所有的组件, 查找切面类
     * 2. 分析切面类, 1个切点 : N 个增强方法  =>  Map
     * 3. 封装切面对象, 定义判断是否增强的方法 和 代理方法
     * 4. 对每个组件进行判断, 如果要增强那么动态代理( CGLIB )
     */

    public static void main(String[] args) throws Exception {
        AOPApplicationContext cxt = new AOPApplicationContext(AopConfig.class);
        final UserBiz userBiz = (UserBiz) cxt.getBean("userBiz");
        userBiz.create(null);
        userBiz.modify(null);
        userBiz.remove(null);
    }

}

// 切面信息类
class AspectInfo {

    // 切点组件对象
    Object aspectObject;
    String pointcutExp;
    // 5 个 增强方法集合
    List<Method> befores = new ArrayList<>();
    List<Method> afters = new ArrayList<>();
    List<Method> afterThrowings = new ArrayList<>();
    List<Method> afterReturnings = new ArrayList<>();
    List<Method> arounds = new ArrayList<>();

    @Override
    public String toString() {
        return "AspectInfo{" +
                "pointcutExp='" + pointcutExp + '\'' +
                ", befores=" + befores +
                ", afters=" + afters +
                ", afterThrowings=" + afterThrowings +
                ", afterReturnings=" + afterReturnings +
                ", arounds=" + arounds +
                '}';
    }
}
